Option Explicit On
Option Strict On

Imports System.Configuration
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.ServiceProcess
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Threading

Public Class ZoneeditService
    Inherits System.ServiceProcess.ServiceBase

    Dim threadListen As New Thread(AddressOf Listen)
    Dim lStopListening As Boolean = False
    Dim lPause As Boolean = False
    Dim port As Integer
    Dim zoneEditUser, zoneEditPwd, zoneEditUri, hostNames As String
    Private LastResponse As String = "haven't Zoneeditd yet..."
    Private mAllOk As Boolean = True
    Private Property AllOk() As Boolean
        Get
            Return mAllOk
        End Get
        Set(ByVal value As Boolean)
            mAllOk = value
            If value Then
                oEventLog.WriteEntry("zoneeditService status ok.")
            Else
                oEventLog.WriteEntry("zoneeditService status problem." + vbCrLf + LastResponse)
            End If

        End Set
    End Property


#Region " Component Designer generated code "

    Public Sub New()
        MyBase.New()

        ' This call is required by the Component Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call

    End Sub

    'UserService overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    ' The main entry point for the process
    <MTAThread()> _
    Shared Sub Main()
        Dim ServicesToRun() As System.ServiceProcess.ServiceBase

        ' More than one NT Service may run within the same process. To add
        ' another service to this process, change the following line to
        ' create a second service object. For example,
        '
        '   ServicesToRun = New System.ServiceProcess.ServiceBase () {New Service1, New MySecondUserService}
        '
        ServicesToRun = New System.ServiceProcess.ServiceBase() {New ZoneeditService}

        System.ServiceProcess.ServiceBase.Run(ServicesToRun)
    End Sub

    'Required by the Component Designer
    Private components As System.ComponentModel.IContainer

    ' NOTE: The following procedure is required by the Component Designer
    ' It can be modified using the Component Designer.  
    ' Do not modify it using the code editor.
    Friend WithEvents oEventLog As System.Diagnostics.EventLog
    Friend WithEvents timerMain As System.Timers.Timer
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.oEventLog = New System.Diagnostics.EventLog
        Me.timerMain = New System.Timers.Timer
        CType(Me.oEventLog, System.ComponentModel.ISupportInitialize).BeginInit()
        CType(Me.timerMain, System.ComponentModel.ISupportInitialize).BeginInit()
        '
        'oEventLog
        '
        Me.oEventLog.Log = "Application"
        Me.oEventLog.Source = "ZoneeditService"
        '
        'timerMain
        '
        Me.timerMain.Enabled = True
        Me.timerMain.Interval = 3600000
        '
        'ZoneeditService
        '
        Me.AutoLog = False
        Me.CanPauseAndContinue = True
        Me.ServiceName = "ZoneeditService"
        CType(Me.oEventLog, System.ComponentModel.ISupportInitialize).EndInit()
        CType(Me.timerMain, System.ComponentModel.ISupportInitialize).EndInit()

    End Sub

#End Region

    Protected Overrides Sub OnStart(ByVal args() As String)
        threadListen.Start()
        'Me.AutoLog = False
        'If Not EventLog.SourceExists("Time Service Log") Then
        '    EventLog.CreateEventSource("Time Service Log", "MyLogFile")
        'End If
        'oEventLog.Source = "Application"
        oEventLog.WriteEntry("zoneeditService Starting")

        Dim portstring As String
        Try
            portstring = My.Settings.servicePort
        Catch ex As Exception
            portstring = "500"
        End Try

        ' oEventLog.WriteEntry("ZoneeditService Started with portstring " + portstring)
        Dim portval As Integer
        If String.IsNullOrEmpty(portstring) OrElse Not Integer.TryParse(portstring, portval) Then
            Me.port = 500
            ' oEventLog.WriteEntry("ZoneeditService defaulting")
        Else
            Me.port = portval
            ' oEventLog.WriteEntry("ZoneeditService got it " + portval.ToString())
        End If

        Me.zoneEditUri = My.Settings.zoneEditUri
        Me.zoneEditUser = My.Settings.zoneEditUser
        Me.zoneEditPwd = My.Settings.zoneEditPwd
        Me.hostNames = My.Settings.hostNames
        oEventLog.WriteEntry("zoneeditService Started listening on port " + port.ToString() + " to update " + Me.hostNames + ".")

        CheckIP()

    End Sub

    Protected Overrides Sub OnStop()
        ' Invoked when the service is stopped 
        lStopListening = True
        oEventLog.WriteEntry("zoneeditService Stopped")
    End Sub

    Protected Overrides Sub OnPause()
        ' Invoked when the service is paused 
        lPause = True
        oEventLog.WriteEntry("zoneeditService Paused")
    End Sub

    Protected Overrides Sub OnContinue()
        ' Invoked when the service is continued 
        lPause = False
        oEventLog.WriteEntry("zoneeditService Continued")

    End Sub

    Private Sub Listen()
        Dim localAdd As System.Net.IPAddress = IPAddress.Parse("127.0.0.1")
        Dim oListener As New TcpListener(localAdd, port)
        Dim aoEventLog As New System.Diagnostics.EventLog

        oListener.Start()
        Do
            If Not lPause Then
                Dim tcpClient As TcpClient = oListener.AcceptTcpClient()
                Dim NWStream As NetworkStream = tcpClient.GetStream
                Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte
                'read incoming stream
                Dim numBytesRead As Integer = NWStream.Read(bytesToRead, 0, CInt(tcpClient.ReceiveBufferSize))

                'write to event log
                ' this causes an error entry in the event log, will have to track down why later
                ' possibly because it is in a thread
                'aoEventLog.WriteEntry("Received :" & Encoding.ASCII.GetString(bytesToRead, 0, numBytesRead) & " @ " & Now)

                'write time back to client
                Dim time() As Byte = Encoding.ASCII.GetBytes(Now.ToString)
                Dim sMessage As String
                Dim bMessage() As Byte

                sMessage = "You Sent: " & Encoding.ASCII.GetString(bytesToRead, 0, numBytesRead)
                sMessage &= vbCrLf & Now.ToString
                bMessage = Encoding.ASCII.GetBytes(sMessage)
                NWStream.Write(bMessage, 0, bMessage.Length)
                tcpClient.Close()
            End If
        Loop Until lStopListening
        oListener.Stop()
    End Sub
    ' overide to receive commands from ExecuteCommand() on winform app
    ' 200 and 201 event log writings work fine
    ' 202 new event log not created, but exception is logged

    Protected Overrides Sub OnCustomCommand(ByVal nCommand As Integer)

        If nCommand = 200 Then
            oEventLog.WriteEntry("Command 200 Executed")
        End If

        If nCommand = 201 Then
            oEventLog.WriteEntry("DOG")
            oEventLog.WriteEntry("CATS")
        End If

        If nCommand = 202 Then
            Try
                If Not EventLog.SourceExists("TEST") Then
                    EventLog.CreateEventSource("TEST", "zoneeditServiceLogFile")
                End If
                'oEventLog.Source = "TEST"
                'oEventLog.WriteEntry("Service Started")
                Dim y As Integer = 4
                Dim x As Integer = 0
                ' Dim z As Integer = Round(y / x)
            Catch ex As Exception

                'oEventLog.Source = "Application"
                oEventLog.WriteEntry(ex.ToString())
            End Try
        End If
    End Sub


    Private Sub timerMain_Elapsed(ByVal sender As System.Object, ByVal e As System.Timers.ElapsedEventArgs) Handles timerMain.Elapsed
        ' oEventLog.WriteEntry("ZoneeditServer timer")
        CheckIP()
        ' timerMain.Stop()

    End Sub

    Sub CheckIP()

        Dim status As String = "unknown"
        Dim currentIP As String = ""
        Try
            ' Console.WriteLine("Checking IP...")
            ' This also works: 
            ' http://dynamic.zoneedit.com/checkip.html
            ' http://www.whatismyip.com/
            Dim myReq As HttpWebRequest = CType(WebRequest.Create("http://automation.whatismyip.com/n09230945.asp"), HttpWebRequest)
            ' myReq.ContentType
            'If myReq.HaveResponse Then
            '    Dim myResponse As WebResponse = myReq.GetResponse()
            'End If
            Dim myResponse As WebResponse, data As Stream
            Try
                myResponse = myReq.GetResponse()
                data = myResponse.GetResponseStream
                Dim reader As New StreamReader(data)
                Dim mypage As String = reader.ReadToEnd()
                reader.Close()
                data.Close()
                myResponse.Close()

                ' Find IP
                Dim m As Match = Regex.Match(mypage, "(?<First>2[0-4]\d|25[0-5]|[01]?\d\d?)\.(?<Second>2[0-4]\d|25[0-5]|[01]?\d\d?)\.(?<Third>2[0-4]\d|25[0-5]|[01]?\d\d?)\.(?<Fourth>2[0-4]\d|25[0-5]|[01]?\d\d?)")
                If Not m.Success Then
                    status = "Could not find IP in response: " & vbCrLf & mypage
                Else
                    status = "Ok"
                    currentIP = m.ToString()
                End If

            Catch ex As System.Net.WebException
                status = "WebException: " + ex.Message

            End Try

        Catch ex As Exception
            status = "GeneralException: " + ex.Message

        End Try

        If status <> "Ok" Then
            LogError(status)
            Exit Sub
        End If

        ' Need to change?
        Dim canConfig As Boolean = True
        Try
            If currentIP = My.Settings.LastIP Then
                LogIt("No change to IP: " & My.Settings.LastIP)
                Exit Sub
            End If
        Catch e As Exception
            LogIt("Exception trying to get settings: " & e.Message)
            canConfig = False
        End Try

        ' Must update
        Dim zoneEditResponse As String = UpdateDns(currentIP, zoneEditUri, hostNames)

        If Not GotSuccess(zoneEditResponse) Then
            LogError("Received error response from ZoneEdit. Please check your configuration." & vbCrLf & _
                    zoneEditResponse)
            canConfig = False
        Else
            LogIt("Update submitted for new IP " & currentIP & ". " & vbCrLf & "ZoneEdit response: " & zoneEditResponse)
        End If

        If canConfig Then
            My.Settings.LastIP = currentIP
            My.Settings.Save()
        End If

    End Sub

    Sub LogIt(ByVal text As String)
        oEventLog.WriteEntry(text & vbCrLf)
    End Sub

    Sub LogError(ByVal text As String)
        oEventLog.WriteEntry(text & vbCrLf, EventLogEntryType.Error)
        LogIt(text)
        ' MsgBox("Problem found in zoneedit - see c:\zoneedit.log")
    End Sub


    '/ <summary>
    '/ Updates ZoneEdit DNS with the current external IP.
    '/ </summary>
    '/ <param name="ip">the IP to send to ZoneEdit</param>
    '/ <param name="hostnames">hostnames requiring a DNS update</param>
    '/ <param name="zoneEditUri">uri to send the update request to</param>
    '/ <returns>the status message from ZoneEdit</returns>
    Private Function UpdateDns(ByVal ip As String, ByVal zoneEditUri As String, ByVal hostnames As String) As String
        Dim body As String
        ' No longer pass new IP - zoneedit derives from caller's IP. This assumes running *from* domain - since that's where we detect the change in the first place
        ' Dim uri As String = zoneEditUri + "?host=" + hostnames + "&dnsto=" + ip
        Dim uri As String = zoneEditUri + "?host=" + hostnames

        Dim req As HttpWebRequest
        Dim res As HttpWebResponse
        Dim sr As StreamReader

        Dim cc As CredentialCache = New CredentialCache()
        Try
            cc.Add(New Uri(zoneEditUri), "Basic", New NetworkCredential(zoneEditUser, zoneEditPwd))
            req = CType(WebRequest.Create(uri), HttpWebRequest)
            req.Method = "GET"
            req.Credentials = cc
            res = CType(req.GetResponse(), HttpWebResponse)
            sr = New StreamReader(res.GetResponseStream())
            body = sr.ReadToEnd()
        Catch e As Exception
            body = "< ERROR " & e.Message & ">"
        Finally
            If sr IsNot Nothing Then
                sr.Close()
            End If
            If res IsNot Nothing Then
                res.Close()
            End If
        End Try

        Return body
    End Function

    '/ <summary>
    '/ Checks whether ZoneEdit's return status was successful.
    '/ </summary>
    '/ <param name="zoneEditResponse">body of the response from ZoneEdit</param>
    '/ <returns>success or failure</returns>
    Private Function GotSuccess(ByVal zoneEditResponse As String) As Boolean
        Dim ret As Boolean = True
        ' break body into individual <...> strings
        Dim re As Regex = New Regex("<\s*(?<status>SUCCESS|ERROR)", RegexOptions.Multiline)
        Dim matches As MatchCollection = re.Matches(zoneEditResponse)
        Dim i As Integer
        For i = 0 To matches.Count - 1 Step i + 1
            If matches(i).Result("${status}") = "ERROR" Then
                ret = False
                Exit For
            End If
        Next

        Return ret
    End Function

End Class